// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h"

#include <X11/Xatom.h>
#include <X11/Xregion.h>
#include <X11/Xutil.h>
#include <X11/extensions/XInput2.h>
#include <X11/extensions/shape.h>
#include <utility>

#include "base/command_line.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "third_party/skia/include/core/SkPath.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/aura/window_property.h"
#include "ui/base/dragdrop/os_exchange_data_provider_aurax11.h"
#include "ui/base/hit_test.h"
#include "ui/base/ime/input_method.h"
#include "ui/base/x/x11_util.h"
#include "ui/base/x/x11_util_internal.h"
#include "ui/events/devices/x11/device_data_manager_x11.h"
#include "ui/events/devices/x11/device_list_cache_x11.h"
#include "ui/events/devices/x11/touch_factory_x11.h"
#include "ui/events/event_utils.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/events/platform/x11/x11_event_source.h"
#include "ui/gfx/display.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/size_conversions.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "ui/gfx/path.h"
#include "ui/gfx/path_x11.h"
#include "ui/gfx/screen.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_aura.h"
#include "ui/views/corewm/tooltip_aura.h"
#include "ui/views/linux_ui/linux_ui.h"
#include "ui/views/views_delegate.h"
#include "ui/views/views_switches.h"
#include "ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.h"
#include "ui/views/widget/desktop_aura/desktop_native_cursor_manager.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_observer_x11.h"
#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
#include "ui/views/widget/desktop_aura/x11_desktop_window_move_client.h"
#include "ui/views/widget/desktop_aura/x11_pointer_grab.h"
#include "ui/views/widget/desktop_aura/x11_window_event_filter.h"
#include "ui/wm/core/compound_event_filter.h"
#include "ui/wm/core/window_util.h"

DECLARE_WINDOW_PROPERTY_TYPE(views::DesktopWindowTreeHostX11*);

namespace views {

DesktopWindowTreeHostX11* DesktopWindowTreeHostX11::g_current_capture = NULL;
std::list<XID>* DesktopWindowTreeHostX11::open_windows_ = NULL;

DEFINE_WINDOW_PROPERTY_KEY(
    aura::Window*, kViewsWindowForRootWindow, NULL);

DEFINE_WINDOW_PROPERTY_KEY(
    DesktopWindowTreeHostX11*, kHostForRootWindow, NULL);

namespace {

    // Constants that are part of EWMH.
    const int k_NET_WM_STATE_ADD = 1;
    const int k_NET_WM_STATE_REMOVE = 0;

    // Special value of the _NET_WM_DESKTOP property which indicates that the window
    // should appear on all desktops.
    const int kAllDesktops = 0xFFFFFFFF;

    const char* kAtomsToCache[] = {
        "UTF8_STRING",
        "WM_DELETE_WINDOW",
        "WM_PROTOCOLS",
        "_NET_FRAME_EXTENTS",
        "_NET_WM_CM_S0",
        "_NET_WM_DESKTOP",
        "_NET_WM_ICON",
        "_NET_WM_NAME",
        "_NET_WM_PID",
        "_NET_WM_PING",
        "_NET_WM_STATE",
        "_NET_WM_STATE_ABOVE",
        "_NET_WM_STATE_FULLSCREEN",
        "_NET_WM_STATE_HIDDEN",
        "_NET_WM_STATE_MAXIMIZED_HORZ",
        "_NET_WM_STATE_MAXIMIZED_VERT",
        "_NET_WM_STATE_SKIP_TASKBAR",
        "_NET_WM_STATE_STICKY",
        "_NET_WM_USER_TIME",
        "_NET_WM_WINDOW_OPACITY",
        "_NET_WM_WINDOW_TYPE",
        "_NET_WM_WINDOW_TYPE_DND",
        "_NET_WM_WINDOW_TYPE_MENU",
        "_NET_WM_WINDOW_TYPE_NORMAL",
        "_NET_WM_WINDOW_TYPE_NOTIFICATION",
        "_NET_WM_WINDOW_TYPE_TOOLTIP",
        "XdndActionAsk",
        "XdndActionCopy"
        "XdndActionLink",
        "XdndActionList",
        "XdndActionMove",
        "XdndActionPrivate",
        "XdndAware",
        "XdndDrop",
        "XdndEnter",
        "XdndFinished",
        "XdndLeave",
        "XdndPosition",
        "XdndProxy", // Proxy windows?
        "XdndSelection",
        "XdndStatus",
        "XdndTypeList",
        NULL
    };

    const char kX11WindowRolePopup[] = "popup";
    const char kX11WindowRoleBubble[] = "bubble";

    // Returns the whole path from |window| to the root.
    std::vector<::Window> GetParentsList(XDisplay* xdisplay, ::Window window)
    {
        ::Window parent_win, root_win;
        Window* child_windows;
        unsigned int num_child_windows;
        std::vector<::Window> result;

        while (window) {
            result.push_back(window);
            if (!XQueryTree(xdisplay, window,
                    &root_win, &parent_win, &child_windows, &num_child_windows))
                break;
            if (child_windows)
                XFree(child_windows);
            window = parent_win;
        }
        return result;
    }

} // namespace

////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostX11, public:

DesktopWindowTreeHostX11::DesktopWindowTreeHostX11(
    internal::NativeWidgetDelegate* native_widget_delegate,
    DesktopNativeWidgetAura* desktop_native_widget_aura)
    : xdisplay_(gfx::GetXDisplay())
    , xwindow_(0)
    , x_root_window_(DefaultRootWindow(xdisplay_))
    , atom_cache_(xdisplay_, kAtomsToCache)
    , window_mapped_(false)
    , is_fullscreen_(false)
    , is_always_on_top_(false)
    , use_native_frame_(false)
    , should_maximize_after_map_(false)
    , use_argb_visual_(false)
    , drag_drop_client_(NULL)
    , native_widget_delegate_(native_widget_delegate)
    , desktop_native_widget_aura_(desktop_native_widget_aura)
    , content_window_(NULL)
    , window_parent_(NULL)
    , custom_window_shape_(false)
    , urgency_hint_set_(false)
    , activatable_(true)
    , close_widget_factory_(this)
{
}

DesktopWindowTreeHostX11::~DesktopWindowTreeHostX11()
{
    window()->ClearProperty(kHostForRootWindow);
    aura::client::SetWindowMoveClient(window(), NULL);
    desktop_native_widget_aura_->OnDesktopWindowTreeHostDestroyed(this);
    DestroyDispatcher();
}

// static
aura::Window* DesktopWindowTreeHostX11::GetContentWindowForXID(XID xid)
{
    aura::WindowTreeHost* host = aura::WindowTreeHost::GetForAcceleratedWidget(xid);
    return host ? host->window()->GetProperty(kViewsWindowForRootWindow) : NULL;
}

// static
DesktopWindowTreeHostX11* DesktopWindowTreeHostX11::GetHostForXID(XID xid)
{
    aura::WindowTreeHost* host = aura::WindowTreeHost::GetForAcceleratedWidget(xid);
    return host ? host->window()->GetProperty(kHostForRootWindow) : NULL;
}

// static
std::vector<aura::Window*> DesktopWindowTreeHostX11::GetAllOpenWindows()
{
    std::vector<aura::Window*> windows(open_windows().size());
    std::transform(open_windows().begin(),
        open_windows().end(),
        windows.begin(),
        GetContentWindowForXID);
    return windows;
}

gfx::Rect DesktopWindowTreeHostX11::GetX11RootWindowBounds() const
{
    return bounds_in_pixels_;
}

gfx::Rect DesktopWindowTreeHostX11::GetX11RootWindowOuterBounds() const
{
    gfx::Rect outer_bounds(bounds_in_pixels_);
    outer_bounds.Inset(-native_window_frame_borders_in_pixels_);
    return outer_bounds;
}

::Region DesktopWindowTreeHostX11::GetWindowShape() const
{
    return window_shape_.get();
}

void DesktopWindowTreeHostX11::HandleNativeWidgetActivationChanged(
    bool active)
{
    if (active) {
        FlashFrame(false);
        OnHostActivated();
        open_windows().remove(xwindow_);
        open_windows().insert(open_windows().begin(), xwindow_);
    } else {
        ReleaseCapture();
    }

    desktop_native_widget_aura_->HandleActivationChanged(active);

    native_widget_delegate_->AsWidget()->GetRootView()->SchedulePaint();
}

void DesktopWindowTreeHostX11::AddObserver(
    views::DesktopWindowTreeHostObserverX11* observer)
{
    observer_list_.AddObserver(observer);
}

void DesktopWindowTreeHostX11::RemoveObserver(
    views::DesktopWindowTreeHostObserverX11* observer)
{
    observer_list_.RemoveObserver(observer);
}

void DesktopWindowTreeHostX11::SwapNonClientEventHandler(
    scoped_ptr<ui::EventHandler> handler)
{
    wm::CompoundEventFilter* compound_event_filter = desktop_native_widget_aura_->root_window_event_filter();
    if (x11_non_client_event_filter_)
        compound_event_filter->RemoveHandler(x11_non_client_event_filter_.get());
    compound_event_filter->AddHandler(handler.get());
    x11_non_client_event_filter_ = std::move(handler);
}

void DesktopWindowTreeHostX11::CleanUpWindowList(
    void (*func)(aura::Window* window))
{
    if (!open_windows_)
        return;
    while (!open_windows_->empty()) {
        XID xid = open_windows_->front();
        func(GetContentWindowForXID(xid));
        if (!open_windows_->empty() && open_windows_->front() == xid)
            open_windows_->erase(open_windows_->begin());
    }

    delete open_windows_;
    open_windows_ = NULL;
}

////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostX11, DesktopWindowTreeHost implementation:

void DesktopWindowTreeHostX11::Init(aura::Window* content_window,
    const Widget::InitParams& params)
{
    content_window_ = content_window;
    activatable_ = (params.activatable == Widget::InitParams::ACTIVATABLE_YES);

    // TODO(erg): Check whether we *should* be building a WindowTreeHost here, or
    // whether we should be proxying requests to another DRWHL.

    // In some situations, views tries to make a zero sized window, and that
    // makes us crash. Make sure we have valid sizes.
    Widget::InitParams sanitized_params = params;
    if (sanitized_params.bounds.width() == 0)
        sanitized_params.bounds.set_width(100);
    if (sanitized_params.bounds.height() == 0)
        sanitized_params.bounds.set_height(100);

    InitX11Window(sanitized_params);
}

void DesktopWindowTreeHostX11::OnNativeWidgetCreated(
    const Widget::InitParams& params)
{
    window()->SetProperty(kViewsWindowForRootWindow, content_window_);
    window()->SetProperty(kHostForRootWindow, this);

    // Ensure that the X11DesktopHandler exists so that it dispatches activation
    // messages to us.
    X11DesktopHandler::get();

    // TODO(erg): Unify this code once the other consumer goes away.
    SwapNonClientEventHandler(
        scoped_ptr<ui::EventHandler>(new X11WindowEventFilter(this)));
    SetUseNativeFrame(params.type == Widget::InitParams::TYPE_WINDOW && !params.remove_standard_frame);

    x11_window_move_client_.reset(new X11DesktopWindowMoveClient);
    aura::client::SetWindowMoveClient(window(), x11_window_move_client_.get());

    SetWindowTransparency();

    native_widget_delegate_->OnNativeWidgetCreated(true);
}

scoped_ptr<corewm::Tooltip> DesktopWindowTreeHostX11::CreateTooltip()
{
    return make_scoped_ptr(new corewm::TooltipAura);
}

scoped_ptr<aura::client::DragDropClient>
DesktopWindowTreeHostX11::CreateDragDropClient(
    DesktopNativeCursorManager* cursor_manager)
{
    drag_drop_client_ = new DesktopDragDropClientAuraX11(
        window(), cursor_manager, xdisplay_, xwindow_);
    drag_drop_client_->Init();
    return make_scoped_ptr(drag_drop_client_);
}

void DesktopWindowTreeHostX11::Close()
{
    // TODO(erg): Might need to do additional hiding tasks here.
    delayed_resize_task_.Cancel();

    if (!close_widget_factory_.HasWeakPtrs()) {
        // And we delay the close so that if we are called from an ATL callback,
        // we don't destroy the window before the callback returned (as the caller
        // may delete ourselves on destroy and the ATL callback would still
        // dereference us when the callback returns).
        base::MessageLoop::current()->PostTask(
            FROM_HERE,
            base::Bind(&DesktopWindowTreeHostX11::CloseNow,
                close_widget_factory_.GetWeakPtr()));
    }
}

void DesktopWindowTreeHostX11::CloseNow()
{
    if (xwindow_ == None)
        return;

    ReleaseCapture();
    native_widget_delegate_->OnNativeWidgetDestroying();

    // If we have children, close them. Use a copy for iteration because they'll
    // remove themselves.
    std::set<DesktopWindowTreeHostX11*> window_children_copy = window_children_;
    for (std::set<DesktopWindowTreeHostX11*>::iterator it = window_children_copy.begin(); it != window_children_copy.end();
         ++it) {
        (*it)->CloseNow();
    }
    DCHECK(window_children_.empty());

    // If we have a parent, remove ourselves from its children list.
    if (window_parent_) {
        window_parent_->window_children_.erase(this);
        window_parent_ = NULL;
    }

    // Remove the event listeners we've installed. We need to remove these
    // because otherwise we get assert during ~WindowEventDispatcher().
    desktop_native_widget_aura_->root_window_event_filter()->RemoveHandler(
        x11_non_client_event_filter_.get());
    x11_non_client_event_filter_.reset();

    // Destroy the compositor before destroying the |xwindow_| since shutdown
    // may try to swap, and the swap without a window causes an X error, which
    // causes a crash with in-process renderer.
    DestroyCompositor();

    open_windows().remove(xwindow_);
    // Actually free our native resources.
    if (ui::PlatformEventSource::GetInstance())
        ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
    XDestroyWindow(xdisplay_, xwindow_);
    xwindow_ = None;

    desktop_native_widget_aura_->OnHostClosed();
}

aura::WindowTreeHost* DesktopWindowTreeHostX11::AsWindowTreeHost()
{
    return this;
}

void DesktopWindowTreeHostX11::ShowWindowWithState(
    ui::WindowShowState show_state)
{
    if (compositor())
        compositor()->SetVisible(true);
    if (!window_mapped_)
        MapWindow(show_state);

    switch (show_state) {
    case ui::SHOW_STATE_MAXIMIZED:
        Maximize();
        break;
    case ui::SHOW_STATE_MINIMIZED:
        Minimize();
        break;
    case ui::SHOW_STATE_FULLSCREEN:
        SetFullscreen(true);
        break;
    default:
        break;
    }

    // Makes the window activated by default if the state is not INACTIVE or
    // MINIMIZED.
    if (show_state != ui::SHOW_STATE_INACTIVE && show_state != ui::SHOW_STATE_MINIMIZED && activatable_) {
        Activate();
    }

    native_widget_delegate_->AsWidget()->SetInitialFocus(show_state);
}

void DesktopWindowTreeHostX11::ShowMaximizedWithBounds(
    const gfx::Rect& restored_bounds)
{
    ShowWindowWithState(ui::SHOW_STATE_MAXIMIZED);
    // Enforce |restored_bounds_in_pixels_| since calling Maximize() could have
    // reset it.
    restored_bounds_in_pixels_ = ToPixelRect(restored_bounds);
}

bool DesktopWindowTreeHostX11::IsVisible() const
{
    return window_mapped_;
}

void DesktopWindowTreeHostX11::SetSize(const gfx::Size& requested_size)
{
    gfx::Size size_in_pixels = ToPixelRect(gfx::Rect(requested_size)).size();
    size_in_pixels = AdjustSize(size_in_pixels);
    bool size_changed = bounds_in_pixels_.size() != size_in_pixels;
    XResizeWindow(xdisplay_, xwindow_, size_in_pixels.width(),
        size_in_pixels.height());
    bounds_in_pixels_.set_size(size_in_pixels);
    if (size_changed) {
        OnHostResized(size_in_pixels);
        ResetWindowRegion();
    }
}

void DesktopWindowTreeHostX11::StackAbove(aura::Window* window)
{
    if (window && window->GetRootWindow()) {
        ::Window window_below = window->GetHost()->GetAcceleratedWidget();
        // Find all parent windows up to the root.
        std::vector<::Window> window_below_parents = GetParentsList(xdisplay_, window_below);
        std::vector<::Window> window_above_parents = GetParentsList(xdisplay_, xwindow_);

        // Find their common ancestor.
        auto it_below_window = window_below_parents.rbegin();
        auto it_above_window = window_above_parents.rbegin();
        for (; it_below_window != window_below_parents.rend() && it_above_window != window_above_parents.rend() && *it_below_window == *it_above_window;
             ++it_below_window, ++it_above_window) {
        }

        if (it_below_window != window_below_parents.rend() && it_above_window != window_above_parents.rend()) {
            // First stack |xwindow_| below so Z-order of |window| stays the same.
            ::Window windows[] = { *it_below_window, *it_above_window };
            if (XRestackWindows(xdisplay_, windows, 2) == 0) {
                // Now stack them properly.
                std::swap(windows[0], windows[1]);
                XRestackWindows(xdisplay_, windows, 2);
            }
        }
    }
}

void DesktopWindowTreeHostX11::StackAtTop()
{
    XRaiseWindow(xdisplay_, xwindow_);
}

void DesktopWindowTreeHostX11::CenterWindow(const gfx::Size& size)
{
    gfx::Size size_in_pixels = ToPixelRect(gfx::Rect(size)).size();
    gfx::Rect parent_bounds_in_pixels = GetWorkAreaBoundsInPixels();

    // If |window_|'s transient parent bounds are big enough to contain |size|,
    // use them instead.
    if (wm::GetTransientParent(content_window_)) {
        gfx::Rect transient_parent_rect = wm::GetTransientParent(content_window_)->GetBoundsInScreen();
        if (transient_parent_rect.height() >= size.height() && transient_parent_rect.width() >= size.width()) {
            parent_bounds_in_pixels = ToPixelRect(transient_parent_rect);
        }
    }

    gfx::Rect window_bounds_in_pixels(
        parent_bounds_in_pixels.x() + (parent_bounds_in_pixels.width() - size_in_pixels.width()) / 2,
        parent_bounds_in_pixels.y() + (parent_bounds_in_pixels.height() - size_in_pixels.height()) / 2,
        size_in_pixels.width(), size_in_pixels.height());
    // Don't size the window bigger than the parent, otherwise the user may not be
    // able to close or move it.
    window_bounds_in_pixels.AdjustToFit(parent_bounds_in_pixels);

    SetBounds(window_bounds_in_pixels);
}

void DesktopWindowTreeHostX11::GetWindowPlacement(
    gfx::Rect* bounds,
    ui::WindowShowState* show_state) const
{
    *bounds = GetRestoredBounds();

    if (IsFullscreen()) {
        *show_state = ui::SHOW_STATE_FULLSCREEN;
    } else if (IsMinimized()) {
        *show_state = ui::SHOW_STATE_MINIMIZED;
    } else if (IsMaximized()) {
        *show_state = ui::SHOW_STATE_MAXIMIZED;
    } else if (!IsActive()) {
        *show_state = ui::SHOW_STATE_INACTIVE;
    } else {
        *show_state = ui::SHOW_STATE_NORMAL;
    }
}

gfx::Rect DesktopWindowTreeHostX11::GetWindowBoundsInScreen() const
{
    return ToDIPRect(bounds_in_pixels_);
}

gfx::Rect DesktopWindowTreeHostX11::GetClientAreaBoundsInScreen() const
{
    // TODO(erg): The NativeWidgetAura version returns |bounds_in_pixels_|,
    // claiming it's needed for View::ConvertPointToScreen() to work correctly.
    // DesktopWindowTreeHostWin::GetClientAreaBoundsInScreen() just asks windows
    // what it thinks the client rect is.
    //
    // Attempts to calculate the rect by asking the NonClientFrameView what it
    // thought its GetBoundsForClientView() were broke combobox drop down
    // placement.
    return GetWindowBoundsInScreen();
}

gfx::Rect DesktopWindowTreeHostX11::GetRestoredBounds() const
{
    // We can't reliably track the restored bounds of a window, but we can get
    // the 90% case down. When *chrome* is the process that requests maximizing
    // or restoring bounds, we can record the current bounds before we request
    // maximization, and clear it when we detect a state change.
    if (!restored_bounds_in_pixels_.IsEmpty())
        return ToDIPRect(restored_bounds_in_pixels_);

    return GetWindowBoundsInScreen();
}

gfx::Rect DesktopWindowTreeHostX11::GetWorkAreaBoundsInScreen() const
{
    return ToDIPRect(GetWorkAreaBoundsInPixels());
}

void DesktopWindowTreeHostX11::SetShape(SkRegion* native_region)
{
    custom_window_shape_ = false;
    window_shape_.reset();

    if (native_region) {
        gfx::Transform transform = GetRootTransform();
        if (!transform.IsIdentity() && !native_region->isEmpty()) {
            SkPath path_in_dip;
            if (native_region->getBoundaryPath(&path_in_dip)) {
                SkPath path_in_pixels;
                path_in_dip.transform(transform.matrix(), &path_in_pixels);
                window_shape_.reset(gfx::CreateRegionFromSkPath(path_in_pixels));
            } else {
                window_shape_.reset(XCreateRegion());
            }
        } else {
            window_shape_.reset(gfx::CreateRegionFromSkRegion(*native_region));
        }

        custom_window_shape_ = true;
        delete native_region;
    }
    ResetWindowRegion();
}

void DesktopWindowTreeHostX11::Activate()
{
    if (!window_mapped_)
        return;

    X11DesktopHandler::get()->ActivateWindow(xwindow_);
}

void DesktopWindowTreeHostX11::Deactivate()
{
    if (!IsActive())
        return;

    ReleaseCapture();
    X11DesktopHandler::get()->DeactivateWindow(xwindow_);
}

bool DesktopWindowTreeHostX11::IsActive() const
{
    return X11DesktopHandler::get()->IsActiveWindow(xwindow_);
}

void DesktopWindowTreeHostX11::Maximize()
{
    if (HasWMSpecProperty("_NET_WM_STATE_FULLSCREEN")) {
        // Unfullscreen the window if it is fullscreen.
        SetWMSpecState(false,
            atom_cache_.GetAtom("_NET_WM_STATE_FULLSCREEN"),
            None);

        // Resize the window so that it does not have the same size as a monitor.
        // (Otherwise, some window managers immediately put the window back in
        // fullscreen mode).
        gfx::Rect adjusted_bounds_in_pixels(bounds_in_pixels_.origin(),
            AdjustSize(bounds_in_pixels_.size()));
        if (adjusted_bounds_in_pixels != bounds_in_pixels_)
            SetBounds(adjusted_bounds_in_pixels);
    }

    // Some WMs do not respect maximization hints on unmapped windows, so we
    // save this one for later too.
    should_maximize_after_map_ = !window_mapped_;

    // When we are in the process of requesting to maximize a window, we can
    // accurately keep track of our restored bounds instead of relying on the
    // heuristics that are in the PropertyNotify and ConfigureNotify handlers.
    restored_bounds_in_pixels_ = bounds_in_pixels_;

    SetWMSpecState(true,
        atom_cache_.GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"),
        atom_cache_.GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"));
    if (IsMinimized())
        ShowWindowWithState(ui::SHOW_STATE_NORMAL);
}

void DesktopWindowTreeHostX11::Minimize()
{
    ReleaseCapture();
    XIconifyWindow(xdisplay_, xwindow_, 0);
}

void DesktopWindowTreeHostX11::Restore()
{
    should_maximize_after_map_ = false;
    SetWMSpecState(false,
        atom_cache_.GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"),
        atom_cache_.GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"));
    if (IsMinimized())
        ShowWindowWithState(ui::SHOW_STATE_NORMAL);
}

bool DesktopWindowTreeHostX11::IsMaximized() const
{
    return (HasWMSpecProperty("_NET_WM_STATE_MAXIMIZED_VERT") && HasWMSpecProperty("_NET_WM_STATE_MAXIMIZED_HORZ"));
}

bool DesktopWindowTreeHostX11::IsMinimized() const
{
    return HasWMSpecProperty("_NET_WM_STATE_HIDDEN");
}

bool DesktopWindowTreeHostX11::HasCapture() const
{
    return g_current_capture == this;
}

void DesktopWindowTreeHostX11::SetAlwaysOnTop(bool always_on_top)
{
    is_always_on_top_ = always_on_top;
    SetWMSpecState(always_on_top,
        atom_cache_.GetAtom("_NET_WM_STATE_ABOVE"),
        None);
}

bool DesktopWindowTreeHostX11::IsAlwaysOnTop() const
{
    return is_always_on_top_;
}

void DesktopWindowTreeHostX11::SetVisibleOnAllWorkspaces(bool always_visible)
{
    SetWMSpecState(always_visible,
        atom_cache_.GetAtom("_NET_WM_STATE_STICKY"),
        None);

    int new_desktop = 0;
    if (always_visible) {
        new_desktop = kAllDesktops;
    } else {
        if (!ui::GetCurrentDesktop(&new_desktop))
            return;
    }

    XEvent xevent;
    memset(&xevent, 0, sizeof(xevent));
    xevent.type = ClientMessage;
    xevent.xclient.window = xwindow_;
    xevent.xclient.message_type = atom_cache_.GetAtom("_NET_WM_DESKTOP");
    xevent.xclient.format = 32;
    xevent.xclient.data.l[0] = new_desktop;
    xevent.xclient.data.l[1] = 0;
    xevent.xclient.data.l[2] = 0;
    xevent.xclient.data.l[3] = 0;
    xevent.xclient.data.l[4] = 0;
    XSendEvent(xdisplay_, x_root_window_, False,
        SubstructureRedirectMask | SubstructureNotifyMask,
        &xevent);
}

bool DesktopWindowTreeHostX11::SetWindowTitle(const base::string16& title)
{
    if (window_title_ == title)
        return false;
    window_title_ = title;
    std::string utf8str = base::UTF16ToUTF8(title);
    XChangeProperty(xdisplay_,
        xwindow_,
        atom_cache_.GetAtom("_NET_WM_NAME"),
        atom_cache_.GetAtom("UTF8_STRING"),
        8,
        PropModeReplace,
        reinterpret_cast<const unsigned char*>(utf8str.c_str()),
        utf8str.size());
    XTextProperty xtp;
    char* c_utf8_str = const_cast<char*>(utf8str.c_str());
    if (Xutf8TextListToTextProperty(xdisplay_, &c_utf8_str, 1,
            XUTF8StringStyle, &xtp)
        == Success) {
        XSetWMName(xdisplay_, xwindow_, &xtp);
        XFree(xtp.value);
    }
    return true;
}

void DesktopWindowTreeHostX11::ClearNativeFocus()
{
    // This method is weird and misnamed. Instead of clearing the native focus,
    // it sets the focus to our |content_window_|, which will trigger a cascade
    // of focus changes into views.
    if (content_window_ && aura::client::GetFocusClient(content_window_) && content_window_->Contains(aura::client::GetFocusClient(content_window_)->GetFocusedWindow())) {
        aura::client::GetFocusClient(content_window_)->FocusWindow(content_window_);
    }
}

Widget::MoveLoopResult DesktopWindowTreeHostX11::RunMoveLoop(
    const gfx::Vector2d& drag_offset,
    Widget::MoveLoopSource source,
    Widget::MoveLoopEscapeBehavior escape_behavior)
{
    aura::client::WindowMoveSource window_move_source = source == Widget::MOVE_LOOP_SOURCE_MOUSE ? aura::client::WINDOW_MOVE_SOURCE_MOUSE : aura::client::WINDOW_MOVE_SOURCE_TOUCH;
    if (x11_window_move_client_->RunMoveLoop(content_window_, drag_offset,
            window_move_source)
        == aura::client::MOVE_SUCCESSFUL)
        return Widget::MOVE_LOOP_SUCCESSFUL;

    return Widget::MOVE_LOOP_CANCELED;
}

void DesktopWindowTreeHostX11::EndMoveLoop()
{
    x11_window_move_client_->EndMoveLoop();
}

void DesktopWindowTreeHostX11::SetVisibilityChangedAnimationsEnabled(
    bool value)
{
    // Much like the previous NativeWidgetGtk, we don't have anything to do here.
}

bool DesktopWindowTreeHostX11::ShouldUseNativeFrame() const
{
    return use_native_frame_;
}

bool DesktopWindowTreeHostX11::ShouldWindowContentsBeTransparent() const
{
    return false;
}

void DesktopWindowTreeHostX11::FrameTypeChanged()
{
    Widget::FrameType new_type = native_widget_delegate_->AsWidget()->frame_type();
    if (new_type == Widget::FRAME_TYPE_DEFAULT) {
        // The default is determined by Widget::InitParams::remove_standard_frame
        // and does not change.
        return;
    }

    SetUseNativeFrame(new_type == Widget::FRAME_TYPE_FORCE_NATIVE);
    // Replace the frame and layout the contents. Even though we don't have a
    // swapable glass frame like on Windows, we still replace the frame because
    // the button assets don't update otherwise.
    native_widget_delegate_->AsWidget()->non_client_view()->UpdateFrame();
}

void DesktopWindowTreeHostX11::SetFullscreen(bool fullscreen)
{
    if (is_fullscreen_ == fullscreen)
        return;
    is_fullscreen_ = fullscreen;
    if (is_fullscreen_)
        delayed_resize_task_.Cancel();

    // Work around a bug where if we try to unfullscreen, metacity immediately
    // fullscreens us again. This is a little flickery and not necessary if
    // there's a gnome-panel, but it's not easy to detect whether there's a
    // panel or not.
    bool unmaximize_and_remaximize = !fullscreen && IsMaximized() && ui::GuessWindowManager() == ui::WM_METACITY;

    if (unmaximize_and_remaximize)
        Restore();
    SetWMSpecState(fullscreen,
        atom_cache_.GetAtom("_NET_WM_STATE_FULLSCREEN"),
        None);
    if (unmaximize_and_remaximize)
        Maximize();

    // Try to guess the size we will have after the switch to/from fullscreen:
    // - (may) avoid transient states
    // - works around Flash content which expects to have the size updated
    //   synchronously.
    // See https://crbug.com/361408
    if (fullscreen) {
        restored_bounds_in_pixels_ = bounds_in_pixels_;
        const gfx::Display display = gfx::Screen::GetScreen()->GetDisplayNearestWindow(window());
        bounds_in_pixels_ = ToPixelRect(display.bounds());
    } else {
        bounds_in_pixels_ = restored_bounds_in_pixels_;
    }
    OnHostMoved(bounds_in_pixels_.origin());
    OnHostResized(bounds_in_pixels_.size());

    if (HasWMSpecProperty("_NET_WM_STATE_FULLSCREEN") == fullscreen) {
        Relayout();
        ResetWindowRegion();
    }
    // Else: the widget will be relaid out either when the window bounds change or
    // when |xwindow_|'s fullscreen state changes.
}

bool DesktopWindowTreeHostX11::IsFullscreen() const
{
    return is_fullscreen_;
}

void DesktopWindowTreeHostX11::SetOpacity(unsigned char opacity)
{
    // X server opacity is in terms of 32 bit unsigned int space, and counts from
    // the opposite direction.
    // XChangeProperty() expects "cardinality" to be long.
    unsigned long cardinality = opacity * 0x1010101;

    if (cardinality == 0xffffffff) {
        XDeleteProperty(xdisplay_, xwindow_,
            atom_cache_.GetAtom("_NET_WM_WINDOW_OPACITY"));
    } else {
        XChangeProperty(xdisplay_, xwindow_,
            atom_cache_.GetAtom("_NET_WM_WINDOW_OPACITY"),
            XA_CARDINAL, 32,
            PropModeReplace,
            reinterpret_cast<unsigned char*>(&cardinality), 1);
    }
}

void DesktopWindowTreeHostX11::SetWindowIcons(
    const gfx::ImageSkia& window_icon, const gfx::ImageSkia& app_icon)
{
    // TODO(erg): The way we handle icons across different versions of chrome
    // could be substantially improved. The Windows version does its own thing
    // and only sometimes comes down this code path. The icon stuff in
    // ChromeViewsDelegate is hard coded to use HICONs. Likewise, we're hard
    // coded to be given two images instead of an arbitrary collection of images
    // so that we can pass to the WM.
    //
    // All of this could be made much, much better.
    std::vector<unsigned long> data;

    if (window_icon.HasRepresentation(1.0f))
        SerializeImageRepresentation(window_icon.GetRepresentation(1.0f), &data);

    if (app_icon.HasRepresentation(1.0f))
        SerializeImageRepresentation(app_icon.GetRepresentation(1.0f), &data);

    if (!data.empty())
        ui::SetAtomArrayProperty(xwindow_, "_NET_WM_ICON", "CARDINAL", data);
}

void DesktopWindowTreeHostX11::InitModalType(ui::ModalType modal_type)
{
    switch (modal_type) {
    case ui::MODAL_TYPE_NONE:
        break;
    default:
        // TODO(erg): Figure out under what situations |modal_type| isn't
        // none. The comment in desktop_native_widget_aura.cc suggests that this
        // is rare.
        NOTIMPLEMENTED();
    }
}

void DesktopWindowTreeHostX11::FlashFrame(bool flash_frame)
{
    if (urgency_hint_set_ == flash_frame)
        return;

    gfx::XScopedPtr<XWMHints> hints(XGetWMHints(xdisplay_, xwindow_));
    if (!hints) {
        // The window hasn't had its hints set yet.
        hints.reset(XAllocWMHints());
    }

    if (flash_frame)
        hints->flags |= XUrgencyHint;
    else
        hints->flags &= ~XUrgencyHint;

    XSetWMHints(xdisplay_, xwindow_, hints.get());

    urgency_hint_set_ = flash_frame;
}

void DesktopWindowTreeHostX11::OnRootViewLayout()
{
    UpdateMinAndMaxSize();
}

void DesktopWindowTreeHostX11::OnNativeWidgetFocus()
{
}

void DesktopWindowTreeHostX11::OnNativeWidgetBlur()
{
}

bool DesktopWindowTreeHostX11::IsAnimatingClosed() const
{
    return false;
}

bool DesktopWindowTreeHostX11::IsTranslucentWindowOpacitySupported() const
{
    return false;
}

void DesktopWindowTreeHostX11::SizeConstraintsChanged()
{
    UpdateMinAndMaxSize();
}

////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostX11, aura::WindowTreeHost implementation:

gfx::Transform DesktopWindowTreeHostX11::GetRootTransform() const
{
    gfx::Display display = gfx::Screen::GetScreen()->GetPrimaryDisplay();
    if (window_mapped_) {
        aura::Window* win = const_cast<aura::Window*>(window());
        display = gfx::Screen::GetScreen()->GetDisplayNearestWindow(win);
    }

    float scale = display.device_scale_factor();
    gfx::Transform transform;
    transform.Scale(scale, scale);
    return transform;
}

ui::EventSource* DesktopWindowTreeHostX11::GetEventSource()
{
    return this;
}

gfx::AcceleratedWidget DesktopWindowTreeHostX11::GetAcceleratedWidget()
{
    return xwindow_;
}

void DesktopWindowTreeHostX11::ShowImpl()
{
    ShowWindowWithState(ui::SHOW_STATE_NORMAL);
    native_widget_delegate_->OnNativeWidgetVisibilityChanged(true);
}

void DesktopWindowTreeHostX11::HideImpl()
{
    if (window_mapped_) {
        XWithdrawWindow(xdisplay_, xwindow_, 0);
        window_mapped_ = false;
    }
    native_widget_delegate_->OnNativeWidgetVisibilityChanged(false);
}

gfx::Rect DesktopWindowTreeHostX11::GetBounds() const
{
    return bounds_in_pixels_;
}

void DesktopWindowTreeHostX11::SetBounds(
    const gfx::Rect& requested_bounds_in_pixel)
{
    gfx::Rect bounds_in_pixels(requested_bounds_in_pixel.origin(),
        AdjustSize(requested_bounds_in_pixel.size()));
    bool origin_changed = bounds_in_pixels_.origin() != bounds_in_pixels.origin();
    bool size_changed = bounds_in_pixels_.size() != bounds_in_pixels.size();
    XWindowChanges changes = { 0 };
    unsigned value_mask = 0;

    if (size_changed) {
        // Update the minimum and maximum sizes in case they have changed.
        UpdateMinAndMaxSize();

        if (bounds_in_pixels.width() < min_size_in_pixels_.width() || bounds_in_pixels.height() < min_size_in_pixels_.height() || (!max_size_in_pixels_.IsEmpty() && (bounds_in_pixels.width() > max_size_in_pixels_.width() || bounds_in_pixels.height() > max_size_in_pixels_.height()))) {
            gfx::Size size_in_pixels = bounds_in_pixels.size();
            if (!max_size_in_pixels_.IsEmpty())
                size_in_pixels.SetToMin(max_size_in_pixels_);
            size_in_pixels.SetToMax(min_size_in_pixels_);
            bounds_in_pixels.set_size(size_in_pixels);
        }

        changes.width = bounds_in_pixels.width();
        changes.height = bounds_in_pixels.height();
        value_mask |= CWHeight | CWWidth;
    }

    if (origin_changed) {
        changes.x = bounds_in_pixels.x();
        changes.y = bounds_in_pixels.y();
        value_mask |= CWX | CWY;
    }
    if (value_mask)
        XConfigureWindow(xdisplay_, xwindow_, value_mask, &changes);

    // Assume that the resize will go through as requested, which should be the
    // case if we're running without a window manager.  If there's a window
    // manager, it can modify or ignore the request, but (per ICCCM) we'll get a
    // (possibly synthetic) ConfigureNotify about the actual size and correct
    // |bounds_in_pixels_| later.
    bounds_in_pixels_ = bounds_in_pixels;

    if (origin_changed)
        native_widget_delegate_->AsWidget()->OnNativeWidgetMove();
    if (size_changed) {
        OnHostResized(bounds_in_pixels.size());
        ResetWindowRegion();
    }
}

gfx::Point DesktopWindowTreeHostX11::GetLocationOnNativeScreen() const
{
    return bounds_in_pixels_.origin();
}

void DesktopWindowTreeHostX11::SetCapture()
{
    if (HasCapture())
        return;

    // Grabbing the mouse is asynchronous. However, we synchronously start
    // forwarding all mouse events received by Chrome to the
    // aura::WindowEventDispatcher which has capture. This makes capture
    // synchronous for all intents and purposes if either:
    // - |g_current_capture|'s X window has capture.
    // OR
    // - The topmost window underneath the mouse is managed by Chrome.
    DesktopWindowTreeHostX11* old_capturer = g_current_capture;

    // Update |g_current_capture| prior to calling OnHostLostWindowCapture() to
    // avoid releasing pointer grab.
    g_current_capture = this;
    if (old_capturer)
        old_capturer->OnHostLostWindowCapture();

    GrabPointer(xwindow_, true, None);
}

void DesktopWindowTreeHostX11::ReleaseCapture()
{
    if (g_current_capture == this) {
        // Release mouse grab asynchronously. A window managed by Chrome is likely
        // the topmost window underneath the mouse so the capture release being
        // asynchronous is likely inconsequential.
        g_current_capture = NULL;
        UngrabPointer();

        OnHostLostWindowCapture();
    }
}

void DesktopWindowTreeHostX11::SetCursorNative(gfx::NativeCursor cursor)
{
    XDefineCursor(xdisplay_, xwindow_, cursor.platform());
}

void DesktopWindowTreeHostX11::MoveCursorToNative(const gfx::Point& location)
{
    XWarpPointer(xdisplay_, None, x_root_window_, 0, 0, 0, 0,
        bounds_in_pixels_.x() + location.x(),
        bounds_in_pixels_.y() + location.y());
}

void DesktopWindowTreeHostX11::OnCursorVisibilityChangedNative(bool show)
{
    // TODO(erg): Conditional on us enabling touch on desktop linux builds, do
    // the same tap-to-click disabling here that chromeos does.
}

////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostX11, private:

void DesktopWindowTreeHostX11::InitX11Window(
    const Widget::InitParams& params)
{
    unsigned long attribute_mask = CWBackPixmap | CWBitGravity;
    XSetWindowAttributes swa;
    memset(&swa, 0, sizeof(swa));
    swa.background_pixmap = None;
    swa.bit_gravity = NorthWestGravity;

    ::Atom window_type;
    switch (params.type) {
    case Widget::InitParams::TYPE_MENU:
        swa.override_redirect = True;
        window_type = atom_cache_.GetAtom("_NET_WM_WINDOW_TYPE_MENU");
        break;
    case Widget::InitParams::TYPE_TOOLTIP:
        swa.override_redirect = True;
        window_type = atom_cache_.GetAtom("_NET_WM_WINDOW_TYPE_TOOLTIP");
        break;
    case Widget::InitParams::TYPE_POPUP:
        swa.override_redirect = True;
        window_type = atom_cache_.GetAtom("_NET_WM_WINDOW_TYPE_NOTIFICATION");
        break;
    case Widget::InitParams::TYPE_DRAG:
        swa.override_redirect = True;
        window_type = atom_cache_.GetAtom("_NET_WM_WINDOW_TYPE_DND");
        break;
    default:
        window_type = atom_cache_.GetAtom("_NET_WM_WINDOW_TYPE_NORMAL");
        break;
    }
    // An in-activatable window should not interact with the system wm.
    if (!activatable_)
        swa.override_redirect = True;

    if (swa.override_redirect)
        attribute_mask |= CWOverrideRedirect;

    Visual* visual;
    int depth;
    ui::ChooseVisualForWindow(&visual, &depth);
    if (depth == 32) {
        attribute_mask |= CWColormap;
        swa.colormap = XCreateColormap(xdisplay_, x_root_window_, visual, AllocNone);

        // x.org will BadMatch if we don't set a border when the depth isn't the
        // same as the parent depth.
        attribute_mask |= CWBorderPixel;
        swa.border_pixel = 0;

        use_argb_visual_ = true;
    }

    bounds_in_pixels_ = ToPixelRect(params.bounds);
    bounds_in_pixels_.set_size(AdjustSize(bounds_in_pixels_.size()));
    xwindow_ = XCreateWindow(xdisplay_, x_root_window_, bounds_in_pixels_.x(),
        bounds_in_pixels_.y(), bounds_in_pixels_.width(),
        bounds_in_pixels_.height(),
        0, // border width
        depth, InputOutput, visual, attribute_mask, &swa);
    if (ui::PlatformEventSource::GetInstance())
        ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
    open_windows().push_back(xwindow_);

    // TODO(erg): Maybe need to set a ViewProp here like in RWHL::RWHL().

    long event_mask = ButtonPressMask | ButtonReleaseMask | FocusChangeMask | KeyPressMask | KeyReleaseMask | EnterWindowMask | LeaveWindowMask | ExposureMask | VisibilityChangeMask | StructureNotifyMask | PropertyChangeMask | PointerMotionMask;
    XSelectInput(xdisplay_, xwindow_, event_mask);
    XFlush(xdisplay_);

    if (ui::IsXInput2Available())
        ui::TouchFactory::GetInstance()->SetupXI2ForXWindow(xwindow_);

    // TODO(erg): We currently only request window deletion events. We also
    // should listen for activation events and anything else that GTK+ listens
    // for, and do something useful.
    ::Atom protocols[2];
    protocols[0] = atom_cache_.GetAtom("WM_DELETE_WINDOW");
    protocols[1] = atom_cache_.GetAtom("_NET_WM_PING");
    XSetWMProtocols(xdisplay_, xwindow_, protocols, 2);

    // We need a WM_CLIENT_MACHINE and WM_LOCALE_NAME value so we integrate with
    // the desktop environment.
    XSetWMProperties(xdisplay_, xwindow_, NULL, NULL, NULL, 0, NULL, NULL, NULL);

    // Likewise, the X server needs to know this window's pid so it knows which
    // program to kill if the window hangs.
    // XChangeProperty() expects "pid" to be long.
    static_assert(sizeof(long) >= sizeof(pid_t),
        "pid_t should not be larger than long");
    long pid = getpid();
    XChangeProperty(xdisplay_,
        xwindow_,
        atom_cache_.GetAtom("_NET_WM_PID"),
        XA_CARDINAL,
        32,
        PropModeReplace,
        reinterpret_cast<unsigned char*>(&pid), 1);

    XChangeProperty(xdisplay_,
        xwindow_,
        atom_cache_.GetAtom("_NET_WM_WINDOW_TYPE"),
        XA_ATOM,
        32,
        PropModeReplace,
        reinterpret_cast<unsigned char*>(&window_type), 1);

    // List of window state properties (_NET_WM_STATE) to set, if any.
    std::vector<::Atom> state_atom_list;

    // Remove popup windows from taskbar unless overridden.
    if ((params.type == Widget::InitParams::TYPE_POPUP || params.type == Widget::InitParams::TYPE_BUBBLE) && !params.force_show_in_taskbar) {
        state_atom_list.push_back(
            atom_cache_.GetAtom("_NET_WM_STATE_SKIP_TASKBAR"));
    }

    // If the window should stay on top of other windows, add the
    // _NET_WM_STATE_ABOVE property.
    is_always_on_top_ = params.keep_on_top;
    if (is_always_on_top_)
        state_atom_list.push_back(atom_cache_.GetAtom("_NET_WM_STATE_ABOVE"));

    if (params.visible_on_all_workspaces) {
        state_atom_list.push_back(atom_cache_.GetAtom("_NET_WM_STATE_STICKY"));
        ui::SetIntProperty(xwindow_, "_NET_WM_DESKTOP", "CARDINAL", kAllDesktops);
    }

    // Setting _NET_WM_STATE by sending a message to the root_window (with
    // SetWMSpecState) has no effect here since the window has not yet been
    // mapped. So we manually change the state.
    if (!state_atom_list.empty()) {
        ui::SetAtomArrayProperty(xwindow_,
            "_NET_WM_STATE",
            "ATOM",
            state_atom_list);
    }

    if (!params.wm_class_name.empty() || !params.wm_class_class.empty()) {
        ui::SetWindowClassHint(
            xdisplay_, xwindow_, params.wm_class_name, params.wm_class_class);
    }

    const char* wm_role_name = NULL;
    // If the widget isn't overriding the role, provide a default value for popup
    // and bubble types.
    if (!params.wm_role_name.empty()) {
        wm_role_name = params.wm_role_name.c_str();
    } else {
        switch (params.type) {
        case Widget::InitParams::TYPE_POPUP:
            wm_role_name = kX11WindowRolePopup;
            break;
        case Widget::InitParams::TYPE_BUBBLE:
            wm_role_name = kX11WindowRoleBubble;
            break;
        default:
            break;
        }
    }
    if (wm_role_name)
        ui::SetWindowRole(xdisplay_, xwindow_, std::string(wm_role_name));

    if (params.remove_standard_frame) {
        // Setting _GTK_HIDE_TITLEBAR_WHEN_MAXIMIZED tells gnome-shell to not force
        // fullscreen on the window when it matches the desktop size.
        ui::SetHideTitlebarWhenMaximizedProperty(xwindow_,
            ui::HIDE_TITLEBAR_WHEN_MAXIMIZED);
    }

    // If we have a parent, record the parent/child relationship. We use this
    // data during destruction to make sure that when we try to close a parent
    // window, we also destroy all child windows.
    if (params.parent && params.parent->GetHost()) {
        XID parent_xid = params.parent->GetHost()->GetAcceleratedWidget();
        window_parent_ = GetHostForXID(parent_xid);
        DCHECK(window_parent_);
        window_parent_->window_children_.insert(this);
    }

    // If we have a delegate which is providing a default window icon, use that
    // icon.
    gfx::ImageSkia* window_icon = ViewsDelegate::GetInstance()
        ? ViewsDelegate::GetInstance()->GetDefaultWindowIcon()
        : NULL;
    if (window_icon) {
        SetWindowIcons(gfx::ImageSkia(), *window_icon);
    }
    CreateCompositor();
    OnAcceleratedWidgetAvailable();
}

gfx::Size DesktopWindowTreeHostX11::AdjustSize(
    const gfx::Size& requested_size_in_pixels)
{
    std::vector<gfx::Display> displays = gfx::Screen::GetScreen()->GetAllDisplays();
    // Compare against all monitor sizes. The window manager can move the window
    // to whichever monitor it wants.
    for (size_t i = 0; i < displays.size(); ++i) {
        if (requested_size_in_pixels == displays[i].GetSizeInPixel()) {
            return gfx::Size(requested_size_in_pixels.width() - 1,
                requested_size_in_pixels.height() - 1);
        }
    }

    // Do not request a 0x0 window size. It causes an XError.
    gfx::Size size_in_pixels = requested_size_in_pixels;
    size_in_pixels.SetToMax(gfx::Size(1, 1));
    return size_in_pixels;
}

void DesktopWindowTreeHostX11::OnWMStateUpdated()
{
    std::vector<::Atom> atom_list;
    // Ignore the return value of ui::GetAtomArrayProperty(). Fluxbox removes the
    // _NET_WM_STATE property when no _NET_WM_STATE atoms are set.
    ui::GetAtomArrayProperty(xwindow_, "_NET_WM_STATE", &atom_list);

    bool was_minimized = IsMinimized();

    window_properties_.clear();
    std::copy(atom_list.begin(), atom_list.end(),
        inserter(window_properties_, window_properties_.begin()));

    // Propagate the window minimization information to the content window, so
    // the render side can update its visibility properly. OnWMStateUpdated() is
    // called by PropertyNofify event from DispatchEvent() when the browser is
    // minimized or shown from minimized state. On Windows, this is realized by
    // calling OnHostResized() with an empty size. In particular,
    // HWNDMessageHandler::GetClientAreaBounds() returns an empty size when the
    // window is minimized. On Linux, returning empty size in GetBounds() or
    // SetBounds() does not work.
    // We also propagate the minimization to the compositor, to makes sure that we
    // don't draw any 'blank' frames that could be noticed in applications such as
    // window manager previews, which show content even when a window is
    // minimized.
    bool is_minimized = IsMinimized();
    if (is_minimized != was_minimized) {
        if (is_minimized) {
            compositor()->SetVisible(false);
            content_window_->Hide();
        } else {
            content_window_->Show();
            compositor()->SetVisible(true);
        }
    }

    if (restored_bounds_in_pixels_.IsEmpty()) {
        DCHECK(!IsFullscreen());
        if (IsMaximized()) {
            // The request that we become maximized originated from a different
            // process. |bounds_in_pixels_| already contains our maximized bounds. Do
            // a best effort attempt to get restored bounds by setting it to our
            // previously set bounds (and if we get this wrong, we aren't any worse
            // off since we'd otherwise be returning our maximized bounds).
            restored_bounds_in_pixels_ = previous_bounds_in_pixels_;
        }
    } else if (!IsMaximized() && !IsFullscreen()) {
        // If we have restored bounds, but WM_STATE no longer claims to be
        // maximized or fullscreen, we should clear our restored bounds.
        restored_bounds_in_pixels_ = gfx::Rect();
    }

    // Ignore requests by the window manager to enter or exit fullscreen (e.g. as
    // a result of pressing a window manager accelerator key). Chrome does not
    // handle window manager initiated fullscreen. In particular, Chrome needs to
    // do preprocessing before the x window's fullscreen state is toggled.

    is_always_on_top_ = HasWMSpecProperty("_NET_WM_STATE_ABOVE");

    // Now that we have different window properties, we may need to relayout the
    // window. (The windows code doesn't need this because their window change is
    // synchronous.)
    Relayout();
    ResetWindowRegion();
}

void DesktopWindowTreeHostX11::OnFrameExtentsUpdated()
{
    std::vector<int> insets;
    if (ui::GetIntArrayProperty(xwindow_, "_NET_FRAME_EXTENTS", &insets) && insets.size() == 4) {
        // |insets| are returned in the order: [left, right, top, bottom].
        native_window_frame_borders_in_pixels_ = gfx::Insets(insets[2], insets[0], insets[3], insets[1]);
    } else {
        native_window_frame_borders_in_pixels_ = gfx::Insets();
    }
}

void DesktopWindowTreeHostX11::UpdateMinAndMaxSize()
{
    if (!window_mapped_)
        return;

    gfx::Size minimum_in_pixels = ToPixelRect(gfx::Rect(native_widget_delegate_->GetMinimumSize())).size();
    gfx::Size maximum_in_pixels = ToPixelRect(gfx::Rect(native_widget_delegate_->GetMaximumSize())).size();
    if (min_size_in_pixels_ == minimum_in_pixels && max_size_in_pixels_ == maximum_in_pixels)
        return;

    min_size_in_pixels_ = minimum_in_pixels;
    max_size_in_pixels_ = maximum_in_pixels;

    XSizeHints hints;
    long supplied_return;
    XGetWMNormalHints(xdisplay_, xwindow_, &hints, &supplied_return);

    if (minimum_in_pixels.IsEmpty()) {
        hints.flags &= ~PMinSize;
    } else {
        hints.flags |= PMinSize;
        hints.min_width = min_size_in_pixels_.width();
        hints.min_height = min_size_in_pixels_.height();
    }

    if (maximum_in_pixels.IsEmpty()) {
        hints.flags &= ~PMaxSize;
    } else {
        hints.flags |= PMaxSize;
        hints.max_width = max_size_in_pixels_.width();
        hints.max_height = max_size_in_pixels_.height();
    }

    XSetWMNormalHints(xdisplay_, xwindow_, &hints);
}

void DesktopWindowTreeHostX11::UpdateWMUserTime(
    const ui::PlatformEvent& event)
{
    if (!IsActive())
        return;

    ui::EventType type = ui::EventTypeFromNative(event);
    if (type == ui::ET_MOUSE_PRESSED || type == ui::ET_KEY_PRESSED || type == ui::ET_TOUCH_PRESSED) {
        unsigned long wm_user_time_ms = static_cast<unsigned long>(
            ui::EventTimeFromNative(event).InMilliseconds());
        XChangeProperty(xdisplay_,
            xwindow_,
            atom_cache_.GetAtom("_NET_WM_USER_TIME"),
            XA_CARDINAL,
            32,
            PropModeReplace,
            reinterpret_cast<const unsigned char*>(&wm_user_time_ms),
            1);
        X11DesktopHandler::get()->set_wm_user_time_ms(wm_user_time_ms);
    }
}

void DesktopWindowTreeHostX11::SetWMSpecState(bool enabled,
    ::Atom state1,
    ::Atom state2)
{
    XEvent xclient;
    memset(&xclient, 0, sizeof(xclient));
    xclient.type = ClientMessage;
    xclient.xclient.window = xwindow_;
    xclient.xclient.message_type = atom_cache_.GetAtom("_NET_WM_STATE");
    xclient.xclient.format = 32;
    xclient.xclient.data.l[0] = enabled ? k_NET_WM_STATE_ADD : k_NET_WM_STATE_REMOVE;
    xclient.xclient.data.l[1] = state1;
    xclient.xclient.data.l[2] = state2;
    xclient.xclient.data.l[3] = 1;
    xclient.xclient.data.l[4] = 0;

    XSendEvent(xdisplay_, x_root_window_, False,
        SubstructureRedirectMask | SubstructureNotifyMask,
        &xclient);
}

bool DesktopWindowTreeHostX11::HasWMSpecProperty(const char* property) const
{
    return window_properties_.find(atom_cache_.GetAtom(property)) != window_properties_.end();
}

void DesktopWindowTreeHostX11::SetUseNativeFrame(bool use_native_frame)
{
    use_native_frame_ = use_native_frame;
    ui::SetUseOSWindowFrame(xwindow_, use_native_frame);
    ResetWindowRegion();
}

void DesktopWindowTreeHostX11::DispatchMouseEvent(ui::MouseEvent* event)
{
    // In Windows, the native events sent to chrome are separated into client
    // and non-client versions of events, which we record on our LocatedEvent
    // structures. On X11, we emulate the concept of non-client. Before we pass
    // this event to the cross platform event handling framework, we need to
    // make sure it is appropriately marked as non-client if it's in the non
    // client area, or otherwise, we can get into a state where the a window is
    // set as the |mouse_pressed_handler_| in window_event_dispatcher.cc
    // despite the mouse button being released.
    //
    // We can't do this later in the dispatch process because we share that
    // with ash, and ash gets confused about event IS_NON_CLIENT-ness on
    // events, since ash doesn't expect this bit to be set, because it's never
    // been set before. (This works on ash on Windows because none of the mouse
    // events on the ash desktop are clicking in what Windows considers to be a
    // non client area.) Likewise, we won't want to do the following in any
    // WindowTreeHost that hosts ash.
    if (content_window_ && content_window_->delegate()) {
        int flags = event->flags();
        int hit_test_code = content_window_->delegate()->GetNonClientComponent(event->location());
        if (hit_test_code != HTCLIENT && hit_test_code != HTNOWHERE)
            flags |= ui::EF_IS_NON_CLIENT;
        event->set_flags(flags);
    }

    // While we unset the urgency hint when we gain focus, we also must remove it
    // on mouse clicks because we can call FlashFrame() on an active window.
    if (event->IsAnyButton() || event->IsMouseWheelEvent())
        FlashFrame(false);

    if (!g_current_capture || g_current_capture == this) {
        SendEventToProcessor(event);
    } else {
        // Another DesktopWindowTreeHostX11 has installed itself as
        // capture. Translate the event's location and dispatch to the other.
        ConvertEventToDifferentHost(event, g_current_capture);
        g_current_capture->SendEventToProcessor(event);
    }
}

void DesktopWindowTreeHostX11::DispatchTouchEvent(ui::TouchEvent* event)
{
    if (g_current_capture && g_current_capture != this && event->type() == ui::ET_TOUCH_PRESSED) {
        ConvertEventToDifferentHost(event, g_current_capture);
        g_current_capture->SendEventToProcessor(event);
    } else {
        SendEventToProcessor(event);
    }
}

void DesktopWindowTreeHostX11::DispatchKeyEvent(ui::KeyEvent* event)
{
    GetInputMethod()->DispatchKeyEvent(event);
}

void DesktopWindowTreeHostX11::ConvertEventToDifferentHost(
    ui::LocatedEvent* located_event,
    DesktopWindowTreeHostX11* host)
{
    DCHECK_NE(this, host);
    const gfx::Display display_src = gfx::Screen::GetScreen()->GetDisplayNearestWindow(window());
    const gfx::Display display_dest = gfx::Screen::GetScreen()->GetDisplayNearestWindow(host->window());
    DCHECK_EQ(display_src.device_scale_factor(),
        display_dest.device_scale_factor());
    gfx::Vector2d offset = GetLocationOnNativeScreen() - host->GetLocationOnNativeScreen();
    gfx::PointF location_in_pixel_in_host = located_event->location_f() + gfx::Vector2dF(offset);
    located_event->set_location_f(location_in_pixel_in_host);
}

void DesktopWindowTreeHostX11::ResetWindowRegion()
{
    // If a custom window shape was supplied then apply it.
    if (custom_window_shape_) {
        XShapeCombineRegion(xdisplay_, xwindow_, ShapeBounding, 0, 0,
            window_shape_.get(), false);
        return;
    }

    window_shape_.reset();

    if (!IsMaximized() && !IsFullscreen()) {
        gfx::Path window_mask;
        views::Widget* widget = native_widget_delegate_->AsWidget();
        if (widget->non_client_view()) {
            // Some frame views define a custom (non-rectangular) window mask. If
            // so, use it to define the window shape. If not, fall through.
            widget->non_client_view()->GetWindowMask(bounds_in_pixels_.size(),
                &window_mask);
            if (window_mask.countPoints() > 0) {
                window_shape_.reset(gfx::CreateRegionFromSkPath(window_mask));
                XShapeCombineRegion(xdisplay_, xwindow_, ShapeBounding, 0, 0,
                    window_shape_.get(), false);
                return;
            }
        }
    }

    // If we didn't set the shape for any reason, reset the shaping information.
    // How this is done depends on the border style, due to quirks and bugs in
    // various window managers.
    if (ShouldUseNativeFrame()) {
        // If the window has system borders, the mask must be set to null (not a
        // rectangle), because several window managers (eg, KDE, XFCE, XMonad) will
        // not put borders on a window with a custom shape.
        XShapeCombineMask(xdisplay_, xwindow_, ShapeBounding, 0, 0, None, ShapeSet);
    } else {
        // Conversely, if the window does not have system borders, the mask must be
        // manually set to a rectangle that covers the whole window (not null). This
        // is due to a bug in KWin <= 4.11.5 (KDE bug #330573) where setting a null
        // shape causes the hint to disable system borders to be ignored (resulting
        // in a double border).
        XRectangle r = { 0,
            0,
            static_cast<unsigned short>(bounds_in_pixels_.width()),
            static_cast<unsigned short>(bounds_in_pixels_.height()) };
        XShapeCombineRectangles(
            xdisplay_, xwindow_, ShapeBounding, 0, 0, &r, 1, ShapeSet, YXBanded);
    }
}

void DesktopWindowTreeHostX11::SerializeImageRepresentation(
    const gfx::ImageSkiaRep& rep,
    std::vector<unsigned long>* data)
{
    int width = rep.GetWidth();
    data->push_back(width);

    int height = rep.GetHeight();
    data->push_back(height);

    const SkBitmap& bitmap = rep.sk_bitmap();
    SkAutoLockPixels locker(bitmap);

    for (int y = 0; y < height; ++y)
        for (int x = 0; x < width; ++x)
            data->push_back(bitmap.getColor(x, y));
}

std::list<XID>& DesktopWindowTreeHostX11::open_windows()
{
    if (!open_windows_)
        open_windows_ = new std::list<XID>();
    return *open_windows_;
}

void DesktopWindowTreeHostX11::MapWindow(ui::WindowShowState show_state)
{
    if (show_state != ui::SHOW_STATE_DEFAULT && show_state != ui::SHOW_STATE_NORMAL && show_state != ui::SHOW_STATE_INACTIVE && show_state != ui::SHOW_STATE_MAXIMIZED) {
        // It will behave like SHOW_STATE_NORMAL.
        NOTIMPLEMENTED();
    }

    // Before we map the window, set size hints. Otherwise, some window managers
    // will ignore toplevel XMoveWindow commands.
    XSizeHints size_hints;
    size_hints.flags = PPosition;
    size_hints.x = bounds_in_pixels_.x();
    size_hints.y = bounds_in_pixels_.y();
    XSetWMNormalHints(xdisplay_, xwindow_, &size_hints);

    // If SHOW_STATE_INACTIVE, tell the window manager not to focus the window
    // when mapping. This is done by setting the _NET_WM_USER_TIME to 0. See e.g.
    // http://standards.freedesktop.org/wm-spec/latest/ar01s05.html
    unsigned long wm_user_time_ms = (show_state == ui::SHOW_STATE_INACTIVE) ? 0 : X11DesktopHandler::get()->wm_user_time_ms();
    if (show_state == ui::SHOW_STATE_INACTIVE || wm_user_time_ms != 0) {
        XChangeProperty(xdisplay_,
            xwindow_,
            atom_cache_.GetAtom("_NET_WM_USER_TIME"),
            XA_CARDINAL,
            32,
            PropModeReplace,
            reinterpret_cast<const unsigned char*>(&wm_user_time_ms),
            1);
    }

    XMapWindow(xdisplay_, xwindow_);

    // We now block until our window is mapped. Some X11 APIs will crash and
    // burn if passed |xwindow_| before the window is mapped, and XMapWindow is
    // asynchronous.
    if (ui::X11EventSource::GetInstance())
        ui::X11EventSource::GetInstance()->BlockUntilWindowMapped(xwindow_);
    window_mapped_ = true;

    UpdateMinAndMaxSize();

    // Some WMs only respect maximize hints after the window has been mapped.
    // Check whether we need to re-do a maximization.
    if (should_maximize_after_map_) {
        Maximize();
        should_maximize_after_map_ = false;
    }
}

void DesktopWindowTreeHostX11::SetWindowTransparency()
{
    compositor()->SetHostHasTransparentBackground(use_argb_visual_);
    window()->SetTransparent(use_argb_visual_);
    content_window_->SetTransparent(use_argb_visual_);
}

void DesktopWindowTreeHostX11::Relayout()
{
    Widget* widget = native_widget_delegate_->AsWidget();
    NonClientView* non_client_view = widget->non_client_view();
    // non_client_view may be NULL, especially during creation.
    if (non_client_view) {
        non_client_view->client_view()->InvalidateLayout();
        non_client_view->InvalidateLayout();
    }
    widget->GetRootView()->Layout();
}

////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHostX11, ui::PlatformEventDispatcher implementation:

bool DesktopWindowTreeHostX11::CanDispatchEvent(
    const ui::PlatformEvent& event)
{
    return event->xany.window == xwindow_ || (event->type == GenericEvent && static_cast<XIDeviceEvent*>(event->xcookie.data)->event == xwindow_);
}

uint32_t DesktopWindowTreeHostX11::DispatchEvent(
    const ui::PlatformEvent& event)
{
    XEvent* xev = event;

    TRACE_EVENT1("views", "DesktopWindowTreeHostX11::Dispatch",
        "event->type", event->type);

    UpdateWMUserTime(event);

    // May want to factor CheckXEventForConsistency(xev); into a common location
    // since it is called here.
    switch (xev->type) {
    case EnterNotify:
    case LeaveNotify: {
        // Ignore EventNotify and LeaveNotify events from children of |xwindow_|.
        // NativeViewGLSurfaceGLX adds a child to |xwindow_|.
        // TODO(pkotwicz|tdanderson): Figure out whether the suppression is
        // necessary. crbug.com/385716
        if (xev->xcrossing.detail == NotifyInferior)
            break;

        ui::MouseEvent mouse_event(xev);
        DispatchMouseEvent(&mouse_event);
        break;
    }
    case Expose: {
        gfx::Rect damage_rect_in_pixels(xev->xexpose.x, xev->xexpose.y,
            xev->xexpose.width, xev->xexpose.height);
        compositor()->ScheduleRedrawRect(damage_rect_in_pixels);
        break;
    }
    case KeyPress: {
        ui::KeyEvent keydown_event(xev);
        DispatchKeyEvent(&keydown_event);
        break;
    }
    case KeyRelease: {
        // There is no way to deactivate a window in X11 so ignore input if
        // window is supposed to be 'inactive'. See comments in
        // X11DesktopHandler::DeactivateWindow() for more details.
        if (!IsActive() && !HasCapture())
            break;

        ui::KeyEvent key_event(xev);
        DispatchKeyEvent(&key_event);
        break;
    }
    case ButtonPress:
    case ButtonRelease: {
        ui::EventType event_type = ui::EventTypeFromNative(xev);
        switch (event_type) {
        case ui::ET_MOUSEWHEEL: {
            ui::MouseWheelEvent mouseev(xev);
            DispatchMouseEvent(&mouseev);
            break;
        }
        case ui::ET_MOUSE_PRESSED:
        case ui::ET_MOUSE_RELEASED: {
            ui::MouseEvent mouseev(xev);
            DispatchMouseEvent(&mouseev);
            break;
        }
        case ui::ET_UNKNOWN:
            // No event is created for X11-release events for mouse-wheel buttons.
            break;
        default:
            NOTREACHED() << event_type;
        }
        break;
    }
    case FocusOut:
        if (xev->xfocus.mode != NotifyGrab) {
            ReleaseCapture();
            OnHostLostWindowCapture();
            X11DesktopHandler::get()->ProcessXEvent(xev);
        } else {
            dispatcher()->OnHostLostMouseGrab();
        }
        break;
    case FocusIn:
        X11DesktopHandler::get()->ProcessXEvent(xev);
        break;
    case ConfigureNotify: {
        DCHECK_EQ(xwindow_, xev->xconfigure.window);
        DCHECK_EQ(xwindow_, xev->xconfigure.event);
        // It's possible that the X window may be resized by some other means than
        // from within aura (e.g. the X window manager can change the size). Make
        // sure the root window size is maintained properly.
        int translated_x_in_pixels = xev->xconfigure.x;
        int translated_y_in_pixels = xev->xconfigure.y;
        if (!xev->xconfigure.send_event && !xev->xconfigure.override_redirect) {
            Window unused;
            XTranslateCoordinates(xdisplay_, xwindow_, x_root_window_, 0, 0,
                &translated_x_in_pixels, &translated_y_in_pixels,
                &unused);
        }
        gfx::Rect bounds_in_pixels(translated_x_in_pixels, translated_y_in_pixels,
            xev->xconfigure.width, xev->xconfigure.height);
        bool size_changed = bounds_in_pixels_.size() != bounds_in_pixels.size();
        bool origin_changed = bounds_in_pixels_.origin() != bounds_in_pixels.origin();
        previous_bounds_in_pixels_ = bounds_in_pixels_;
        bounds_in_pixels_ = bounds_in_pixels;

        if (origin_changed)
            OnHostMoved(bounds_in_pixels_.origin());

        if (size_changed) {
            delayed_resize_task_.Reset(base::Bind(
                &DesktopWindowTreeHostX11::DelayedResize,
                close_widget_factory_.GetWeakPtr(), bounds_in_pixels.size()));
            base::MessageLoop::current()->PostTask(
                FROM_HERE, delayed_resize_task_.callback());
        }
        break;
    }
    case GenericEvent: {
        ui::TouchFactory* factory = ui::TouchFactory::GetInstance();
        if (!factory->ShouldProcessXI2Event(xev))
            break;

        ui::EventType type = ui::EventTypeFromNative(xev);
        XEvent last_event;
        int num_coalesced = 0;

        switch (type) {
        case ui::ET_TOUCH_MOVED:
            num_coalesced = ui::CoalescePendingMotionEvents(xev, &last_event);
            if (num_coalesced > 0)
                xev = &last_event;
            // fallthrough
        case ui::ET_TOUCH_PRESSED:
        case ui::ET_TOUCH_RELEASED: {
            ui::TouchEvent touchev(xev);
            DispatchTouchEvent(&touchev);
            break;
        }
        case ui::ET_MOUSE_MOVED:
        case ui::ET_MOUSE_DRAGGED:
        case ui::ET_MOUSE_PRESSED:
        case ui::ET_MOUSE_RELEASED:
        case ui::ET_MOUSE_ENTERED:
        case ui::ET_MOUSE_EXITED: {
            if (type == ui::ET_MOUSE_MOVED || type == ui::ET_MOUSE_DRAGGED) {
                // If this is a motion event, we want to coalesce all pending motion
                // events that are at the top of the queue.
                num_coalesced = ui::CoalescePendingMotionEvents(xev, &last_event);
                if (num_coalesced > 0)
                    xev = &last_event;
            }
            ui::MouseEvent mouseev(xev);
            DispatchMouseEvent(&mouseev);
            break;
        }
        case ui::ET_MOUSEWHEEL: {
            ui::MouseWheelEvent mouseev(xev);
            DispatchMouseEvent(&mouseev);
            break;
        }
        case ui::ET_SCROLL_FLING_START:
        case ui::ET_SCROLL_FLING_CANCEL:
        case ui::ET_SCROLL: {
            ui::ScrollEvent scrollev(xev);
            SendEventToProcessor(&scrollev);
            break;
        }
        case ui::ET_KEY_PRESSED:
        case ui::ET_KEY_RELEASED: {
            ui::KeyEvent key_event(xev);
            DispatchKeyEvent(&key_event);
            break;
        }
        case ui::ET_UNKNOWN:
            break;
        default:
            NOTREACHED();
        }

        // If we coalesced an event we need to free its cookie.
        if (num_coalesced > 0)
            XFreeEventData(xev->xgeneric.display, &last_event.xcookie);
        break;
    }
    case MapNotify: {
        FOR_EACH_OBSERVER(DesktopWindowTreeHostObserverX11,
            observer_list_,
            OnWindowMapped(xwindow_));
        break;
    }
    case UnmapNotify: {
        FOR_EACH_OBSERVER(DesktopWindowTreeHostObserverX11,
            observer_list_,
            OnWindowUnmapped(xwindow_));
        break;
    }
    case ClientMessage: {
        Atom message_type = xev->xclient.message_type;
        if (message_type == atom_cache_.GetAtom("WM_PROTOCOLS")) {
            Atom protocol = static_cast<Atom>(xev->xclient.data.l[0]);
            if (protocol == atom_cache_.GetAtom("WM_DELETE_WINDOW")) {
                // We have received a close message from the window manager.
                OnHostCloseRequested();
            } else if (protocol == atom_cache_.GetAtom("_NET_WM_PING")) {
                XEvent reply_event = *xev;
                reply_event.xclient.window = x_root_window_;

                XSendEvent(xdisplay_,
                    reply_event.xclient.window,
                    False,
                    SubstructureRedirectMask | SubstructureNotifyMask,
                    &reply_event);
            }
        } else if (message_type == atom_cache_.GetAtom("XdndEnter")) {
            drag_drop_client_->OnXdndEnter(xev->xclient);
        } else if (message_type == atom_cache_.GetAtom("XdndLeave")) {
            drag_drop_client_->OnXdndLeave(xev->xclient);
        } else if (message_type == atom_cache_.GetAtom("XdndPosition")) {
            drag_drop_client_->OnXdndPosition(xev->xclient);
        } else if (message_type == atom_cache_.GetAtom("XdndStatus")) {
            drag_drop_client_->OnXdndStatus(xev->xclient);
        } else if (message_type == atom_cache_.GetAtom("XdndFinished")) {
            drag_drop_client_->OnXdndFinished(xev->xclient);
        } else if (message_type == atom_cache_.GetAtom("XdndDrop")) {
            drag_drop_client_->OnXdndDrop(xev->xclient);
        }
        break;
    }
    case MappingNotify: {
        switch (xev->xmapping.request) {
        case MappingModifier:
        case MappingKeyboard:
            XRefreshKeyboardMapping(&xev->xmapping);
            break;
        case MappingPointer:
            ui::DeviceDataManagerX11::GetInstance()->UpdateButtonMap();
            break;
        default:
            NOTIMPLEMENTED() << " Unknown request: " << xev->xmapping.request;
            break;
        }
        break;
    }
    case MotionNotify: {
        // Discard all but the most recent motion event that targets the same
        // window with unchanged state.
        XEvent last_event;
        while (XPending(xev->xany.display)) {
            XEvent next_event;
            XPeekEvent(xev->xany.display, &next_event);
            if (next_event.type == MotionNotify && next_event.xmotion.window == xev->xmotion.window && next_event.xmotion.subwindow == xev->xmotion.subwindow && next_event.xmotion.state == xev->xmotion.state) {
                XNextEvent(xev->xany.display, &last_event);
                xev = &last_event;
            } else {
                break;
            }
        }

        ui::MouseEvent mouseev(xev);
        DispatchMouseEvent(&mouseev);
        break;
    }
    case PropertyNotify: {
        ::Atom changed_atom = xev->xproperty.atom;
        if (changed_atom == atom_cache_.GetAtom("_NET_WM_STATE"))
            OnWMStateUpdated();
        else if (changed_atom == atom_cache_.GetAtom("_NET_FRAME_EXTENTS"))
            OnFrameExtentsUpdated();
        break;
    }
    case SelectionNotify: {
        drag_drop_client_->OnSelectionNotify(xev->xselection);
        break;
    }
    }
    return ui::POST_DISPATCH_STOP_PROPAGATION;
}

void DesktopWindowTreeHostX11::DelayedResize(const gfx::Size& size_in_pixels)
{
    OnHostResized(size_in_pixels);
    ResetWindowRegion();
    delayed_resize_task_.Cancel();
}

gfx::Rect DesktopWindowTreeHostX11::GetWorkAreaBoundsInPixels() const
{
    std::vector<int> value;
    if (ui::GetIntArrayProperty(x_root_window_, "_NET_WORKAREA", &value) && value.size() >= 4) {
        return gfx::Rect(value[0], value[1], value[2], value[3]);
    }

    // Fetch the geometry of the root window.
    Window root;
    int x, y;
    unsigned int width, height;
    unsigned int border_width, depth;
    if (!XGetGeometry(xdisplay_, x_root_window_, &root, &x, &y, &width, &height,
            &border_width, &depth)) {
        NOTIMPLEMENTED();
        return gfx::Rect(0, 0, 10, 10);
    }

    return gfx::Rect(x, y, width, height);
}

gfx::Rect DesktopWindowTreeHostX11::ToDIPRect(
    const gfx::Rect& rect_in_pixels) const
{
    gfx::RectF rect_in_dip = gfx::RectF(rect_in_pixels);
    GetRootTransform().TransformRectReverse(&rect_in_dip);
    return gfx::ToEnclosingRect(rect_in_dip);
}

gfx::Rect DesktopWindowTreeHostX11::ToPixelRect(
    const gfx::Rect& rect_in_dip) const
{
    gfx::RectF rect_in_pixels = gfx::RectF(rect_in_dip);
    GetRootTransform().TransformRect(&rect_in_pixels);
    return gfx::ToEnclosingRect(rect_in_pixels);
}

////////////////////////////////////////////////////////////////////////////////
// DesktopWindowTreeHost, public:

// static
DesktopWindowTreeHost* DesktopWindowTreeHost::Create(
    internal::NativeWidgetDelegate* native_widget_delegate,
    DesktopNativeWidgetAura* desktop_native_widget_aura)
{
    return new DesktopWindowTreeHostX11(native_widget_delegate,
        desktop_native_widget_aura);
}

// static
ui::NativeTheme* DesktopWindowTreeHost::GetNativeTheme(aura::Window* window)
{
    const views::LinuxUI* linux_ui = views::LinuxUI::instance();
    if (linux_ui) {
        ui::NativeTheme* native_theme = linux_ui->GetNativeTheme(window);
        if (native_theme)
            return native_theme;
    }

    return ui::NativeThemeAura::instance();
}

} // namespace views
